package com.bph.schedule.algorithm;

import com.bph.commons.pojo.User;
import com.bph.commons.utils.SP;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Objects;

/*todo
 *  按周排班，一定是周一至周日。
 * 考虑当前月份第一天是否为周一，最后一天是否为周日，排班周期应该在覆盖整个月的基础上始于周一，止于周日
 * 因此周累计时长不能在周日排班结束后不加考虑地清除
 *          如果月初不是周一，那本月月初排班时如何获取上个月的周累计时长，月底不是周日，周时长不清空，下个月又如何获得？
 *                  存在数据库里？存在前端cookie里面？
 * 规则分为硬固定规则（不可改）、软固定规则（管理员可改）、自定义规则（店长可改）
 * 排班结束后将开放班次信息返给前端，在数据库中不能有异常数据或空值
 * 客流数据：前端post请求上传Excel文件，后端接收，存到数据库中
 * 前端下载排班表，后端返回Excel文件给前端
 * 规则和偏好都调用相应模块去获取
 * 开店前和关店后的排班可与营业时间班次放在一起考虑
 * 员工每个班次结束后必须休息
 * 就餐时间被完全覆盖的员工休息时间延长半小时
 * 返回员工总工作时长给前端
 *  */

//排班逻辑
public class ScheduleHandler {

    //开放班次  时间段+需求人数
    public static LinkedHashMap<String,Integer> empty = new LinkedHashMap<>();

    public static LinkedHashMap<String, ArrayList<User>> schedule
            (ArrayList<LinkedHashMap<String, Integer>> scheduleRequired,
             ArrayList<LinkedHashMap<String,Object>> employeesInfo){

        //最终生成的月总排班表
        LinkedHashMap<String, ArrayList<User>> monthSchedule = new LinkedHashMap<>();

        //最终生成的排班表（日排班表）
        LinkedHashMap<String, ArrayList<User>> schedule = new LinkedHashMap<>();

        //开放班次定义（未启用）
        User openSchedule = new User();
        openSchedule.setUser_id(0);
        openSchedule.setUser_name("开放班次");
        openSchedule.setUser_title("XXX");

        //开始排班
        int d = 0;
       for(LinkedHashMap<String,Integer> need:scheduleRequired){//循环一个月中的每一天
           for(String stage:need.keySet()){//循环一天中n个有需求的班次

               //todo 这班次会有超过22:00的，原因是最低2小时限制，该如何考虑？
               String[] stages = SP.str_Parse(stage);//获取班次

               double hours = Double.parseDouble(stages[1])-Double.parseDouble(stages[0]);//获取该班次时长

               int needNum = need.get(stage);//获取该班次需求人数

               ArrayList<User> user_arr = new ArrayList<>();//用于存放每个班次被安排的员工

               //先根据stage去"加工"employeeInfo，获取排班需要参考的"标记"
               EmployeeFilter.filterByStatus(stage,employeesInfo);
               EmployeeFilter.filterByTime(stage,employeesInfo);

               //按weekTime排一次序，不然一周内每天的排班会是一样的
               employeesInfo = Data.sortByWeekTime(employeesInfo);

               int flag = 0;//标志该班次已经排了多少人

               int hobbyNum = 2;
               //stage_degree == 1.0 && time_degree >= 1.0
               boolean[] bool = new boolean[hobbyNum];
               Arrays.fill(bool,true);
               int depth = 0;
               for(int i=0;i<employeesInfo.size();i++){
                   if(flag == needNum)
                       break;
                   //获取员工"标记"
                   double stage_degree = Double.parseDouble(String.valueOf(employeesInfo.get(i).get("stage_degree")));
                   double time_degree = Double.parseDouble(String.valueOf(employeesInfo.get(i).get("time_degree")));
                   String dayMax = String.valueOf(employeesInfo.get(i).get("dayMax"));
                   String relax = String.valueOf(employeesInfo.get(i).get("must_break"));
                   String lunch = String.valueOf(employeesInfo.get(i).get("lunch"));
                   String supper = String.valueOf(employeesInfo.get(i).get("supper"));
                   double weekTime = Double.parseDouble(String.valueOf(employeesInfo.get(i).get("weekTime")));
                   //该员工本周累计工作时长未达到最大值
                   if(weekTime<Data.weekMax){
                       //pass掉 日累计达8小时的人、当前班次是饭点但还没吃饭的人、已经连上4小时必须休息的人
                       if(dayMax.equals("0")&&relax.equals("0")&&lunch.equals("0")&&supper.equals("0")) {
                           //获取员工基本信息
                           User user = new User();
                           user.setUser_id(Integer.parseInt(String.valueOf(employeesInfo.get(i).get("user_id"))));
                           user.setUser_name(String.valueOf(employeesInfo.get(i).get("user_name")));
                           user.setUser_title(String.valueOf(employeesInfo.get(i).get("user_title")));
                           boolean hobby1 = stage_degree==1.0;
                           boolean hobby2 = time_degree >= 1.0;
                           //如果偏好班次和时长都完全满足，排上
                           if(hobby1==bool[0] && hobby2==bool[1]){
                               String oldSchedule = String.valueOf(employeesInfo.get(i).get("dSchedule"));
                               employeesInfo.get(i).put("dSchedule",oldSchedule+","+stage);
                               String oldWeekTime = String.valueOf(employeesInfo.get(i).get("weekTime"));
                               String newWeekTime = String.valueOf(Double.parseDouble(oldWeekTime)+hours);
                               employeesInfo.get(i).put("weekTime",newWeekTime);
                               flag ++;
                           }
                       }
                   }
                   //第一级别的筛选已经结束，接下来降低级别
                   //降低级别：怎么降？底线是什么? 不同的设定对应最终的员工满意度不同
                   //官方解释：最优化排班表是指满足所有配置的规则下未分配的班次最少，工时数最低。
                   /*规则不能违背，只能降低偏好要求
                    *虽然官方的指标是最少未分配班次数，但出于人性化考虑，引入了偏好满意度
                    * 偏好满意度如何计算？各偏好权重不同的话在降低偏好要求时就不能随便降
                    **/
                   if(i==employeesInfo.size()-1){
                       i = 0; //先将员工表指针拨回表头
                       //只允许一个偏好不满足
                       Arrays.fill(bool,true);
                       if(bool.length-1-depth>=0){
                           bool[bool.length-1-depth] = false;
                       }else{
                           //允许两个偏好都不满足
                          Arrays.fill(bool,false);
                       }
                       depth += 1;
                       if(depth == 4)
                           break;
                   }
               }
               if(flag != needNum){
                   empty.put(stages[0]+","+stages[1],needNum-flag);
               }

           }//一天排班结束

           //每排完一天，employeeInfo里面的daySchedule都应该清空
           //清空前先放入总排班表中
           for(LinkedHashMap<String,Object> userMap:employeesInfo){
               String dSchedule = String.valueOf(userMap.get("dSchedule"));
               User user = new User();
               user.setUser_id(Integer.parseInt(String.valueOf(userMap.get("user_id"))));
               user.setUser_name(String.valueOf(userMap.get("user_name")));
               user.setUser_title(String.valueOf(userMap.get("user_title")));
               String[] dS = SP.str_Parse(dSchedule);
               for(int i=0;i+1<dS.length;i=i+2){
                   double start = Double.parseDouble(dS[i]);
                   double end = Double.parseDouble(dS[i+1]);
                   while(start<end){
                     String time_key = start+","+(start+0.5);
                     if(schedule.containsKey(time_key)){
                        schedule.get(time_key).add(user);
                     }else{
                         ArrayList<User> arr = new ArrayList<>();
                         arr.add(user);
                         schedule.put(time_key,arr);
                     }
                       start += 0.5;
                    }
               }
           }

           schedule = Data.sortByKey(schedule);//按时间排一下序
           for(double begin=8;begin<=21.5;begin+=0.5){
               String key = begin+","+(begin+0.5);
               if(!schedule.containsKey(key)){
                   ArrayList<User> arr = new ArrayList<>();
                   arr.add(openSchedule);
                   schedule.put(key,arr);
               }
           }

           //清空所有员工的日排班情况
           for(LinkedHashMap<String,Object> info:employeesInfo){
               info.put("dSchedule","0,0");
           }

           //todo 目前有缺陷，班次可能超过22点,故先删去超过的部分信息
           //但是map不支持读写并发，如何解决？
           //日排班表写入月总排班表,时间键加上日期
           int u =0,count=0;
           for(ArrayList<User> dS:schedule.values()){
               count++;
               if(count<=28){
                   monthSchedule.put(Data.Date[u+d*28],dS);
                   u++;
               }
           }
           String date = SP.str_Parse(Data.Date[d*28])[0].substring(0,10);
           String week = DateUtil.dateToWeek(date);

           //清空日排班表
           schedule = new LinkedHashMap<>();
           d++;
           //如果当前是周日，则把周累计时长置为0
           if("星期日".equals(week)){
               for(LinkedHashMap<String,Object> e:employeesInfo){
                   e.put("weekTime","0");
               }
           }
           //如果今天是本月最后一天但不是周日
           if(d==scheduleRequired.size() && !("星期日".equals(week))){
               //todo 周时长传给前端
           }
       } //本月排班结束

        //打印测试一下
        System.out.println("本月排班表如下：");
        int count = 0;
        for(String time: monthSchedule.keySet()){
            count++;
            if(count%28==1)
                System.out.println("第"+(count/28+1)+"天排班表-->");
            System.out.println("班次时间："+time);
            ArrayList<User> arr = monthSchedule.get(time);
            System.out.println("需求人数："+arr.size());
            System.out.println("排班结果：");
            if(arr.size() == 1 && Objects.equals(arr.get(0).getUser_name(), "开放班次")){
                System.out.println("该班次未匹配到合适的员工");
            }else{
                for(int i=0;i<arr.size();i++){
                    System.out.print(arr.get(i).getUser_name()+"-");
                }
            }
            System.out.println("\n");
        }
        System.out.println("开放班次信息如下：");
        for(String tmp: empty.keySet()){
            System.out.println(tmp+":"+empty.get(tmp)+"人");
        }
        return monthSchedule;
    }

}
